Задълбочено изследване на споделянето на ресурси между различни източници (CORS) и preflight заявките. Научете как да се справяте с CORS проблеми и да защитите уеб приложенията си.
Демаскиране на CORS: Задълбочен поглед върху обработката на Preflight заявки в JavaScript
В постоянно разширяващия се свят на уеб разработката сигурността е от първостепенно значение. Споделянето на ресурси между различни източници (CORS) е ключов механизъм за сигурност, внедрен от уеб браузърите, за да ограничи уеб страниците от отправяне на заявки към домейн, различен от този, който е обслужил уеб страницата. Това е фундаментална функция за сигурност, предназначена да предотврати достъпа на злонамерени уебсайтове до чувствителни данни. Това изчерпателно ръководство ще се потопи в тънкостите на CORS, като се фокусира специално върху обработката на preflight заявките. Ще разгледаме „защо“, „какво“ и „как“ на CORS, предоставяйки практически примери и решения на често срещани проблеми, с които се сблъскват разработчиците по целия свят.
Разбиране на политиката за същия произход (Same-Origin Policy)
В основата на CORS лежи Политиката за същия произход (Same-Origin Policy - SOP). Тази политика е механизъм за сигурност на ниво браузър, който ограничава скриптове, изпълнявани от един произход, да достъпват ресурси от друг произход. Произходът се дефинира от протокола (напр. HTTP или HTTPS), домейна (напр. example.com) и порта (напр. 80 или 443). Два URL адреса имат един и същ произход, ако тези три компонента съвпадат точно.
Например:
https://www.example.com/app1/index.html
иhttps://www.example.com/app2/index.html
имат един и същ произход (същият протокол, домейн и порт).https://www.example.com/index.html
иhttp://www.example.com/index.html
имат различен произход (различни протоколи).https://www.example.com/index.html
иhttps://api.example.com/index.html
имат различен произход (различните поддомейни се считат за различни домейни).https://www.example.com:8080/index.html
иhttps://www.example.com/index.html
имат различен произход (различни портове).
SOP е предназначена да предотврати достъпа на злонамерени скриптове от един уебсайт до чувствителни данни, като бисквитки или информация за удостоверяване на потребителя, на друг уебсайт. Въпреки че е от съществено значение за сигурността, SOP може да бъде и ограничаваща, особено когато са необходими легитимни заявки между различни източници.
Какво е споделяне на ресурси между различни източници (CORS)?
CORS е механизъм, който позволява на сървърите да посочат кои произходи (домейни, схеми или портове) имат право да достъпват техните ресурси. Той по същество облекчава SOP, позволявайки контролиран достъп между различни източници. CORS се прилага чрез HTTP хедъри, които се обменят между клиента (обикновено уеб браузър) и сървъра.
Когато браузърът прави заявка към друг произход (т.е. заявка към произход, различен от текущата страница), той първо проверява дали сървърът позволява заявката. Това се прави чрез изследване на хедъра Access-Control-Allow-Origin
в отговора на сървъра. Ако произходът на заявката е посочен в този хедър (или ако хедърът е настроен на *
, позволявайки всички произходи), браузърът позволява на заявката да продължи. В противен случай браузърът блокира заявката, предотвратявайки достъпа на JavaScript кода до данните в отговора.
Ролята на Preflight заявките
За определени типове заявки между различни източници браузърът инициира preflight заявка. Това е OPTIONS
заявка, изпратена до сървъра преди действителната заявка. Целта на preflight заявката е да се определи дали сървърът е готов да приеме действителната заявка. Сървърът отговаря на preflight заявката с информация за позволените методи, хедъри и други ограничения.
Preflight заявките се задействат, когато заявката между различни източници отговаря на някое от следните условия:
- Методът на заявката не е
GET
,HEAD
илиPOST
. - Заявката включва персонализирани хедъри (т.е. хедъри, различни от тези, които се добавят автоматично от браузъра).
- Хедърът
Content-Type
е зададен на нещо различно отapplication/x-www-form-urlencoded
,multipart/form-data
илиtext/plain
. - Заявката използва обекти
ReadableStream
в тялото си.
Например, PUT
заявка с Content-Type
от application/json
ще задейства preflight заявка, защото използва различен метод от позволените и потенциално непозволен тип съдържание.
Защо са необходими Preflight заявките?
Preflight заявките са от съществено значение за сигурността, защото предоставят на сървъра възможност да отхвърли потенциално вредни заявки между различни източници, преди те да бъдат изпълнени. Без preflight заявки злонамерен уебсайт би могъл потенциално да изпраща произволни заявки до сървър без изричното съгласие на сървъра. Preflight заявката позволява на сървъра да валидира, че заявката е приемлива, и предотвратява потенциално вредни операции.
Обработка на Preflight заявки от страна на сървъра
Правилната обработка на preflight заявките е от решаващо значение за гарантиране на правилното и сигурно функциониране на вашето уеб приложение. Сървърът трябва да отговори на OPTIONS
заявката с подходящите CORS хедъри, за да посочи дали действителната заявка е разрешена.
Ето разбивка на ключовите CORS хедъри, които се използват в preflight отговорите:
Access-Control-Allow-Origin
: Този хедър указва произхода(ите), които имат право да достъпват ресурса. Може да бъде настроен на конкретен произход (напр.https://www.example.com
) или на*
, за да се разрешат всички произходи. Въпреки това използването на*
обикновено не се препоръчва от съображения за сигурност, особено ако сървърът обработва чувствителни данни.Access-Control-Allow-Methods
: Този хедър указва HTTP методите, които са разрешени за заявката между различни източници (напр.GET
,POST
,PUT
,DELETE
).Access-Control-Allow-Headers
: Този хедър указва списъка с нестандартни HTTP хедъри, които са разрешени в действителната заявка. Това е необходимо, ако клиентът изпраща персонализирани хедъри, катоX-Custom-Header
илиAuthorization
.Access-Control-Allow-Credentials
: Този хедър показва дали действителната заявка може да включва идентификационни данни, като бисквитки или хедъри за упълномощаване. Трябва да е настроен наtrue
, ако клиентският код изпраща идентификационни данни и сървърът трябва да ги приеме. Забележка: когато този хедър е настроен на `true`, `Access-Control-Allow-Origin` *не може* да бъде настроен на `*`. Трябва да посочите конкретен произход.Access-Control-Max-Age
: Този хедър указва максималното време (в секунди), за което браузърът може да кешира preflight отговора. Това може да помогне за подобряване на производителността чрез намаляване на броя на изпратените preflight заявки.
Пример: Обработка на Preflight заявки в Node.js с Express
Ето пример как да обработвате preflight заявки в Node.js приложение, използващо Express framework:
const express = require('express');
const cors = require('cors');
const app = express();
// Разрешаване на CORS за всички произходи (само за целите на разработката!)
// В производствена среда посочете разрешените произходи за по-добра сигурност.
app.use(cors()); //или app.use(cors({origin: 'https://www.example.com'}));
// Маршрут за обработка на OPTIONS заявки (preflight)
app.options('/data', cors()); // Разрешаване на CORS за един маршрут. Или посочете произход: cors({origin: 'https://www.example.com'})
// Маршрут за обработка на GET заявки
app.get('/data', (req, res) => {
res.json({ message: 'This is cross-origin data!' });
});
// Маршрут за обработка на preflight и post заявка
app.options('/resource', cors()); // разрешаване на pre-flight заявка за DELETE заявка
app.delete('/resource', cors(), (req, res, next) => {
res.send('delete resource')
})
const port = 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
В този пример използваме cors
middleware за обработка на CORS заявки. За по-детайлен контрол, CORS може да бъде разрешен за всеки маршрут поотделно. Забележка: в производствена среда е силно препоръчително да се посочат разрешените произходи, като се използва опцията origin
, вместо да се разрешават всички произходи. Разрешаването на всички произходи с помощта на *
може да изложи вашето приложение на уязвимости в сигурността.
Пример: Обработка на Preflight заявки в Python с Flask
Ето пример как да обработвате preflight заявки в Python приложение, използващо Flask framework и разширението flask_cors
:
from flask import Flask, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app) # Разрешаване на CORS за всички маршрути
@app.route('/data')
@cross_origin()
def get_data():
data = {"message": "This is cross-origin data!"}
return jsonify(data)
if __name__ == '__main__':
app.run(debug=True)
Това е най-простата употреба. Както и преди, произходите могат да бъдат ограничени. Вижте документацията на flask-cors за подробности.
Пример: Обработка на Preflight заявки в Java със Spring Boot
Ето пример как да обработвате preflight заявки в Java приложение, използващо Spring Boot:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class CorsApplication {
public static void main(String[] args) {
SpringApplication.run(CorsApplication.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/data").allowedOrigins("http://localhost:8080");
}
};
}
}
И съответният контролер:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DataController {
@GetMapping("/data")
public String getData() {
return "This is cross-origin data!";
}
}
Често срещани проблеми с CORS и техните решения
Въпреки важността си, CORS често може да бъде източник на разочарование за разработчиците. Ето някои често срещани проблеми с CORS и техните решения:
-
Грешка: "No 'Access-Control-Allow-Origin' header is present on the requested resource."
Тази грешка показва, че сървърът не връща хедъра
Access-Control-Allow-Origin
в своя отговор. За да коригирате това, уверете се, че сървърът е конфигуриран да включва хедъра и че той е настроен на правилния произход или на*
(ако е подходящо).Решение: Конфигурирайте сървъра да включва хедъра `Access-Control-Allow-Origin` в своя отговор, като го настроите на произхода на изискващия уебсайт или на `*`, за да разрешите всички произходи (използвайте с повишено внимание).
-
Грешка: "Response to preflight request doesn't pass access control check: Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers in preflight response."
Тази грешка показва, че сървърът не разрешава персонализирания хедър (
X-Custom-Header
в този пример) в заявката между различни източници. За да коригирате това, уверете се, че сървърът включва хедъра вAccess-Control-Allow-Headers
хедъра в preflight отговора.Решение: Добавете персонализирания хедър (напр. `X-Custom-Header`) към хедъра `Access-Control-Allow-Headers` в preflight отговора на сървъра.
-
Грешка: "Credentials flag is 'true', but the 'Access-Control-Allow-Origin' header is '*'."
Когато хедърът
Access-Control-Allow-Credentials
е настроен наtrue
, хедърътAccess-Control-Allow-Origin
трябва да бъде настроен на конкретен произход, а не на*
. Това е така, защото разрешаването на идентификационни данни от всички произходи би представлявало риск за сигурността.Решение: Когато използвате идентификационни данни, настройте `Access-Control-Allow-Origin` на конкретен произход вместо на `*`.
-
Preflight заявката не се изпраща.
Проверете отново дали вашият Javascript код включва свойството `credentials: 'include'`. Проверете също дали вашият сървър разрешава `Access-Control-Allow-Credentials: true`.
-
Противоречиви конфигурации между сървъра и клиента.
Внимателно проверете вашата сървърна CORS конфигурация заедно с настройките от страна на клиента. Несъответствията (напр. сървърът разрешава само GET заявки, но клиентът изпраща POST) ще причинят CORS грешки.
CORS и добри практики за сигурност
Въпреки че CORS позволява контролиран достъп между различни източници, е изключително важно да се следват добрите практики за сигурност, за да се предотвратят уязвимости:
- Избягвайте използването на
*
в хедъраAccess-Control-Allow-Origin
в производствена среда. Това позволява на всички произходи да достъпват вашите ресурси, което може да бъде риск за сигурността. Вместо това посочете точните произходи, които са разрешени. - Внимателно обмислете кои методи и хедъри да разрешите. Разрешавайте само методите и хедърите, които са строго необходими за правилното функциониране на вашето приложение.
- Внедрете подходящи механизми за автентикация и оторизация. CORS не е заместител на автентикацията и оторизацията. Уверете се, че вашето API е защитено с подходящи мерки за сигурност.
- Валидирайте и почиствайте всички потребителски данни. Това помага за предотвратяване на атаки от тип Cross-Site Scripting (XSS) и други уязвимости.
- Поддържайте сървърната си CORS конфигурация актуална. Редовно преглеждайте и актуализирайте вашата CORS конфигурация, за да сте сигурни, че тя съответства на изискванията за сигурност на вашето приложение.
CORS в различни среди за разработка
Проблемите с CORS могат да се проявят по различен начин в различните среди и технологии за разработка. Ето поглед върху това как да подходим към CORS в няколко често срещани сценария:
Локални среди за разработка
По време на локална разработка проблемите с CORS могат да бъдат особено досадни. Браузърите често блокират заявки от вашия локален сървър за разработка (напр. localhost:3000
) към отдалечено API. Няколко техники могат да облекчат този проблем:
- Разширения за браузър: Разширения като "Allow CORS: Access-Control-Allow-Origin" могат временно да деактивират CORS ограниченията за целите на тестването. Въпреки това, *никога* не ги използвайте в производствена среда.
- Прокси сървъри: Конфигурирайте прокси сървър, който препраща заявки от вашия локален сървър за разработка към отдалеченото API. Това ефективно прави заявките "от същия произход" от гледна точка на браузъра. Инструменти като
http-proxy-middleware
(за Node.js) са полезни за тази цел. - Конфигуриране на CORS на сървъра: Дори по време на разработка е добра практика да конфигурирате вашия API сървър изрично да разрешава заявки от вашия локален произход за разработка (напр.
http://localhost:3000
). Това симулира реална CORS конфигурация и ви помага да откриете проблеми на ранен етап.
Безсървърни среди (напр. AWS Lambda, Google Cloud Functions)
Безсървърните функции често изискват внимателна CORS конфигурация. Много безсървърни платформи предоставят вградена поддръжка за CORS, но е изключително важно да я конфигурирате правилно:
- Специфични за платформата настройки: Използвайте вградените опции за CORS конфигурация на платформата. AWS Lambda, например, ви позволява да посочите разрешени произходи, методи и хедъри директно в настройките на API Gateway.
- Middleware/Библиотеки: За по-голяма гъвкавост можете да използвате middleware или библиотеки за обработка на CORS в кода на вашата безсървърна функция. Това е подобно на подходите, използвани в традиционните сървърни среди (напр. използването на пакета `cors` в Node.js Lambda функции).
- Обмислете метода
OPTIONS
: Уверете се, че вашата безсървърна функция обработваOPTIONS
заявките правилно. Това често включва създаването на отделен маршрут, който връща подходящите CORS хедъри.
Разработка на мобилни приложения (напр. React Native, Flutter)
CORS е по-малко пряка грижа за нативните мобилни приложения (Android, iOS), тъй като те обикновено не налагат политиката за същия произход по същия начин като уеб браузърите. Въпреки това, CORS все още може да бъде релевантен, ако вашето мобилно приложение използва web view за показване на уеб съдържание или ако използвате фреймуърци като React Native или Flutter, които използват JavaScript:
- Web Views: Ако вашето мобилно приложение използва web view за показване на уеб съдържание, се прилагат същите CORS правила като в уеб браузър. Конфигурирайте сървъра си да разрешава заявки от произхода на уеб съдържанието.
- React Native/Flutter: Тези фреймуърци използват JavaScript за отправяне на API заявки. Въпреки че нативната среда може да не налага CORS директно, базовите HTTP клиенти (напр.
fetch
) все още могат да проявяват подобно на CORS поведение в определени ситуации. - Нативни HTTP клиенти: Когато правите API заявки директно от нативен код (напр. използвайки OkHttp на Android или URLSession на iOS), CORS обикновено не е фактор. Въпреки това, все още трябва да имате предвид най-добрите практики за сигурност като правилна автентикация и оторизация.
Глобални съображения при CORS конфигурацията
Когато конфигурирате CORS за глобално достъпно приложение, е изключително важно да се вземат предвид фактори като:
- Суверенитет на данните: Регулациите в някои региони изискват данните да се съхраняват в рамките на региона. CORS може да бъде замесен при достъп до ресурси през граници, което потенциално може да наруши законите за пребиваване на данни.
- Регионални политики за сигурност: Различните държави може да имат различни регулации и насоки за киберсигурност, които влияят върху начина, по който CORS трябва да бъде внедрен и защитен.
- Мрежи за доставка на съдържание (CDNs): Уверете се, че вашата CDN е правилно конфигурирана да предава необходимите CORS хедъри. Неправилно конфигурираните CDN мрежи могат да премахнат CORS хедърите, което води до неочаквани грешки.
- Балансиращи устройства и проксита: Проверете дали всички балансиращи устройства или обратни проксита във вашата инфраструктура обработват правилно preflight заявките и предават CORS хедърите.
- Многоезична поддръжка: Обмислете как CORS взаимодейства със стратегиите за интернационализация (i18n) и локализация (l10n) на вашето приложение. Уверете се, че CORS политиките са последователни в различните езикови версии на вашето приложение.
Тестване и отстраняване на грешки в CORS
Ефективното тестване и отстраняване на грешки в CORS е от жизненоважно значение. Ето някои техники:
- Инструменти за разработчици в браузъра: Конзолата за разработчици на браузъра е първото място, където трябва да проверите. Табът "Network" ще покаже preflight заявките и отговорите, разкривайки дали CORS хедърите присъстват и са правилно конфигурирани.
- Инструментът `curl` от командния ред: Използвайте `curl -v -X OPTIONS
`, за да изпратите ръчно preflight заявки и да инспектирате хедърите в отговора на сървъра. - Онлайн инструменти за проверка на CORS: Множество онлайн инструменти могат да помогнат за валидирането на вашата CORS конфигурация. Просто потърсете "CORS checker".
- Единични и интеграционни тестове: Напишете автоматизирани тестове, за да проверите дали вашата CORS конфигурация работи според очакванията. Тези тестове трябва да покриват както успешни заявки между различни източници, така и сценарии, при които CORS трябва да блокира достъпа.
- Записване на логове и мониторинг: Внедрете записване на логове за проследяване на събития, свързани с CORS, като preflight заявки и блокирани заявки. Наблюдавайте логовете си за подозрителна активност или грешки в конфигурацията.
Заключение
Споделянето на ресурси между различни източници (CORS) е жизненоважен механизъм за сигурност, който позволява контролиран достъп до уеб ресурси между различни източници. Разбирането на начина, по който работи CORS, особено preflight заявките, е от решаващо значение за изграждането на сигурни и надеждни уеб приложения. Като следвате добрите практики, очертани в това ръководство, можете ефективно да се справяте с проблемите с CORS и да защитите приложението си от потенциални уязвимости. Не забравяйте винаги да давате приоритет на сигурността и внимателно да обмисляте последиците от вашата CORS конфигурация.
С развитието на уеб разработката CORS ще продължи да бъде критичен аспект на уеб сигурността. Информираността за най-новите добри практики и техники на CORS е от съществено значение за изграждането на сигурни и глобално достъпни уеб приложения.